<?php

/**
 * Ares ApiBaseController class file
 * @author xingkun
 * @date 2014/11/07 
 */
class ApiBaseController extends ApiCController {

    /**
     * default respose format
     * @var string
     */
    private $format = 'json';

    /**
     * construct
     */
    public function __construct() {
        $sid = array_key_exists('accessToken', $_REQUEST) ? $_REQUEST['accessToken'] : '';
        $this->initSession($sid);
    }

    /**
     * 初始化session
     * 
     * @param  string $sid sessionID
     * @return void
     */
    public function initSession($sid = '') {

        if (empty($sid)) {
            Yii::app()->getSession()->open();
        } else {
            Yii::app()->getSession()->close();
            Yii::app()->getSession()->setSessionID($sid);
            Yii::app()->getSession()->open();
        }
    }

    /**
     * 初始化session数据
     * 
     * @param  string $customer_id 用户ID
     * @return void
     */
    public function initSessionData($customer_id) {
        // 在线后填充会话数据
        if (intval($customer_id) > 0) {
            $objMember = Customer::model()->findByPk($customer_id);
            $arrMember = OBJTool::convertModelToArray($objMember);
            $dbPassword = $arrMember['password'];
            unset($arrMember['password']);
            $objUserIdentity = new UserIdentity($username, $password);
            $objUserIdentity->setUserIdentity($arrMember, $arrMember['customer_id'], $dbPassword, $arrMember['username']);
            Yii::app()->apiUser->setState(LoginUser::LOGIN_USER_INFO_KEY, $objUserIdentity->getLoginData());
            Yii::app()->apiUser->setState(LoginUser::LOGIN_USER_ID_KEY, $objUserIdentity->getUserId());
            Yii::app()->apiUser->setState(LoginUser::LOGIN_USER_USERNAME_KEY, $objUserIdentity->getUserName());
            Yii::app()->apiUser->setState("customer_id", $customer_id);
            Yii::app()->apiUser->setState("token", AresUtil::generateBase64Token($customer_id));
        }
    }

    /**
     * validate all system parameters
     * 
     * @param  array $requestParams 请求参数
     * @return void
     */
    public function validateSysParams($requestParams) {

        $allSysParams = ApiDefinition::$sysParams;
        $cfgAllowedAppKeys = ApiDefinition::$cfgAllowedAppKeys;
        $cfgAllowedAppSecrects = ApiDefinition::$cfgAllowedAppSecrects;
        $cfgAllowedApiVersions = ApiDefinition::$cfgAllowedApiVersions;

        /**
         * 验证所有必须系统级参数，存在且非空
         */
        foreach ($allSysParams as $field => $rule) {
            // 必选参数且没有设置值
            if (($rule === null) && !array_key_exists($field, $requestParams)) {
                $missedSysParams[] = $field;
            } elseif (($rule === null) && ($requestParams[$field] == '')) { //且值为空
                $emptySysParams[] = $field;
            }
        }

        // send error
        if (!empty($missedSysParams)) {
            $this->sendApiError('1000020', array('parameter' => implode(', ', $missedSysParams)));
        }
        // send error
        if (!empty($emptySysParams)) {
            $this->sendApiError('1000021', array('parameter' => implode(', ', $emptySysParams)));
        }

        /**
         * 验证有效性
         */
        // 验证client
        $client = $requestParams['client'];
        if (!empty($client) && !array_key_exists($client, $cfgAllowedAppKeys)) {
            $this->sendApiError('1000006');
        }
        // 验证API版本
        $client = $requestParams['client'];
        if (!empty($requestParams['v']) && !in_array($requestParams['v'], $cfgAllowedApiVersions)) {
            $this->sendApiError('1000005');
        }
        // 验证app key
        $appKey = $cfgAllowedAppKeys[$client];
        if (!empty($requestParams['app_key']) && ($requestParams['app_key'] != $appKey)) {
            $this->sendApiError('1000007');
        }
        /**
         * 验证签名
         */
        $appSecrect = $cfgAllowedAppSecrects[$client];
        $signedParamsStr = AresApiUtil::signURLParameters($requestParams, array('sign'), $appSecrect);
        if ($requestParams['sign'] != $signedParamsStr) {
            $this->sendApiError('1000018');
        }
    }

    /**
     * validate all api parameters
     * 
     * @param  array $requestParams 请求参数
     * @return void
     */
    public function validateApiParams($requestParams) {

        $apiDefinition = ApiDefinition::$apiMapping;
        $api = trim($requestParams['r']);
        $api = strtolower($api);
        if (array_key_exists($api, $apiDefinition)) {
            // 验证请求方法类型
            $apiVerb = $apiDefinition[$api]['verb'];
            if ($apiVerb != $_SERVER['REQUEST_METHOD']) {
                $this->sendApiError('1000008');//modify
            }

            // 验证参数
            $apiParams = $apiDefinition[$api]['parameters'];
            if (!empty($apiParams)) {
                $missedRequiredApiParams = array();
                $emptyRequiredApiParams = array();
                foreach ($apiParams as $field => $rule) {
                    // 跳过fields验证
                    if ($field == 'fields') {
                        continue;
                    }
                    // 必选参数且没有设置值
                    if (($rule === null) && !array_key_exists($field, $requestParams)) {
                        $missedRequiredApiParams[] = $field;
                    } elseif (($rule === null) && ($requestParams[$field] == '')) { //且值为空
                        $emptyRequiredApiParams[] = $field;
                    }
                } //end foreach
                // send error
                if (!empty($missedRequiredApiParams)) {
                    $this->sendApiError('1000090', array('parameter' => implode(', ', $missedRequiredApiParams)));
                }
                // send error
                if (!empty($emptyRequiredApiParams)) {
                    $this->sendApiError('1000091', array('parameter' => implode(', ', $emptyRequiredApiParams)));
                }
            }
        } else {
            // send error
            $this->sendApiError('1000001', array('parameter' => $api));
        }
    }

    /**
     * format API result
     *
     * @param string $data
     * @param array  $dataKey 返回数据的key
     * @access public
     * @return void
     */
    public function sendResult($data = array(), $dataKey = '') {

        //根据key格式话结果数组
        if ($dataKey == '') {
            $result = empty($data) ? array() : $data;
        } else {
            $result = empty($data) ? array($dataKey => array()) : array($dataKey => $data);
        }

        //根据结果状态格式化返回结果
        $formatedData = array(
            'code' => '0',
            'errMsg' => 'success',
            'data' => $result,
        );

        //render
        $this->_sendResponse(200, CJSON::encode($formatedData), 'application/json');
    }

    /**
     * format API error result
     *
     * @param string $errCode
     * @param array  $errMsgVars 参数array需指定key，通过key占位
     * @param string $errMsg
     * @access public
     * @return void
     */
    public function sendApiError($errCode, $errMsgVars = array(), $errMsg = '') {

        //获取预定义错误消息格式
        $definedApiErrorMapping = ApiError::$apiErrorMapping;
        $errMsgFormat = ($errMsg == '') ? $definedApiErrorMapping[$errCode] : $errMsg;

        //格式化预定义错误模板
        if (is_array($errMsgVars) && !empty($errMsgVars)) {
            $formatedErrorMsg = $this->_sprintfArray($errMsgFormat, $errMsgVars);
        } else {
            $formatedErrorMsg = $errMsgFormat;
        }

        //根据结果状态格式化返回结果
        $formatedData = array(
            'code' => $errCode,
            'errMsg' => $formatedErrorMsg,
        );

        //render
        $this->_sendResponse(200, CJSON::encode($formatedData), 'application/json');
    }

    /**
     * format System error result
     *
     * @param string $errCode
     * @param array  $errMsgVars 参数array需指定key，通过key占位
     * @param string $errMsg
     * @access public
     * @return void
     */
    public function sendHttpError($httpCode, $errMsgVars = array(), $errMsg = '') {
        //获取预定义错误消息格式
        $definedErrorMapping = ApiError::$httpErrorMapping;
        $errMsgFormat = ($errMsg == '') ? $definedErrorMapping[$httpCode] : $errMsg;

        //格式化预定义错误模板
        if (is_array($errMsgVars) && !empty($errMsgVars)) {
            $formatedErrorMsg = $this->_sprintfArray($errMsgFormat, $errMsgVars);
        } else {
            $formatedErrorMsg = $errMsgFormat;
        }

        //render
        $this->_sendResponse($httpCode, $formatedErrorMsg);
    }

    /**
     * Checks if a request is authorized
     * 
     * @access public
     * @return void
     */
    public function checkToken($token) {
        $token = $this->getString($token);
        if (empty($token)) {
            // send error
            $this->sendApiError('1000090', array('parameter' => 'token'));
        }

        // 解析token获取customer id, 并验证token有效性
        $customer_id = AresUtil::restoreFromBase64Token($token);

        // 判断是否为有效ID，并保持在会话中
        // 保证可以长久有效保持登录状态
        if (!empty($customer_id) && is_int($customer_id)) {
            $this->initSessionData($customer_id);
        }

        // 验证token有效性, 基于安全考虑
        if (Yii::app()->apiUser->token != $token) {
            // send error
            $this->sendApiError('1000011');
        }
    }

    /*     * ******************** Private Functions ********************* */

    /**
     * Sends the API response 
     * 
     * @param int $status 
     * @param string $body 
     * @param string $content_type 
     * @access private
     * @return void
     */
    private function _sendResponse($status = 200, $body = '', $content_type = 'text/html') {
        $status_header = 'HTTP/1.1 ' . $status . ' ' . $this->_getStatusCodeMessage($status);
        // set the status
        header($status_header);
        // set the content type
        header('Content-type: ' . $content_type);

        // pages with body are easy
        if ($body != '') {
            // send the body
            echo $body;
            exit;
        }
        // we need to create the body if none is passed
        else {
            // create some body messages
            $message = '';

            // this is purely optional, but makes the pages a little nicer to read
            // for your users.  Since you won't likely send a lot of different status codes,
            // this also shouldn't be too ponderous to maintain
            switch ($status) {
                case 401:
                    $message = 'You must be authorized to view this page.';
                    break;
                case 404:
                    $message = 'The requested URL ' . $_SERVER['REQUEST_URI'] . ' was not found.';
                    break;
                case 500:
                    $message = 'The server encountered an error processing your request.';
                    break;
                case 501:
                    $message = 'The requested method is not implemented.';
                    break;
            }

            // servers don't always have a signature turned on (this is an apache directive "ServerSignature On")
            $signature = ($_SERVER['SERVER_SIGNATURE'] == '') ? $_SERVER['SERVER_SOFTWARE'] . ' Server at ' . $_SERVER['SERVER_NAME'] . ' Port ' . $_SERVER['SERVER_PORT'] : $_SERVER['SERVER_SIGNATURE'];

            // this should be templatized in a real-world solution
            $body = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
                        <html>
                            <head>
                                <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
                                <title>' . $status . ' ' . $this->_getStatusCodeMessage($status) . '</title>
                            </head>
                            <body>
                                <h1>' . $this->_getStatusCodeMessage($status) . '</h1>
                                <p>' . $message . '</p>
                                <hr />
                                <address>' . $signature . '</address>
                            </body>
                        </html>';

            echo $body;
            exit;
        }
    }

    /**
     * Gets the message for a status code
     * 
     * @param mixed $status 
     * @access private
     * @return string
     */
    private function _getStatusCodeMessage($status) {
        // these could be stored in a .ini file and loaded
        // via parse_ini_file()... however, this will suffice
        // for an example
        $codes = Array(
            100 => 'Continue',
            101 => 'Switching Protocols',
            200 => 'OK',
            201 => 'Created',
            202 => 'Accepted',
            203 => 'Non-Authoritative Information',
            204 => 'No Content',
            205 => 'Reset Content',
            206 => 'Partial Content',
            300 => 'Multiple Choices',
            301 => 'Moved Permanently',
            302 => 'Found',
            303 => 'See Other',
            304 => 'Not Modified',
            305 => 'Use Proxy',
            306 => '(Unused)',
            307 => 'Temporary Redirect',
            400 => 'Bad Request',
            401 => 'Unauthorized',
            402 => 'Payment Required',
            403 => 'Forbidden',
            404 => 'Not Found',
            405 => 'Method Not Allowed',
            406 => 'Not Acceptable',
            407 => 'Proxy Authentication Required',
            408 => 'Request Timeout',
            409 => 'Conflict',
            410 => 'Gone',
            411 => 'Length Required',
            412 => 'Precondition Failed',
            413 => 'Request Entity Too Large',
            414 => 'Request-URI Too Long',
            415 => 'Unsupported Media Type',
            416 => 'Requested Range Not Satisfiable',
            417 => 'Expectation Failed',
            500 => 'Internal Server Error',
            501 => 'Not Implemented',
            502 => 'Bad Gateway',
            503 => 'Service Unavailable',
            504 => 'Gateway Timeout',
            505 => 'HTTP Version Not Supported'
        );

        return (isset($codes[$status])) ? $codes[$status] : '';
    }

    /**
     * Returns the json or xml encoded array
     * 
     * @param mixed $model 
     * @param mixed $array Data to be encoded
     * @access private
     * @return void
     */
    private function _getObjectEncoded($model, $array) {
        if (isset($_REQUEST['format'])) {
            $this->format = $_REQUEST['format'];
        }
        if ($this->format == 'json') {
            return CJSON::encode($array);
        } elseif ($this->format == 'xml') {
            $result = '<?xml version="1.0">';
            $result .= "\n<$model>\n";
            foreach ($array as $key => $value)
                $result .= "    <$key>" . utf8_encode($value) . "</$key>\n";
            $result .= '</' . $model . '>';
            return $result;
        } else {
            return;
        }
    }

    /**
     * customer defined function for sprinf_array where named arguments are desired (php syntax)
     * 参考python的字典型字符串格式化
     *
     * with sprintf: sprintf('second: %2$s ; first: %1$s', '1st', '2nd');
     * 
     * with sprintf_array: sprintf_array('second: %(second)s ; first: %(first)04d', array(
     *  'first' => '42',
     *  'second'=> '2nd'
     * ));
     * 
     *
     * @param string $format sprintf format string, with any number of named arguments
     * @param array $args array of [ 'arg_name' => 'arg value', ... ] replacements to be made
     * @access private
     * @return string|false result of sprintf call, or bool false on error
     */
    private function _sprintfArray($string, $array) {
        $keys = array_keys($array);
        $keysmap = array_flip($keys);
        $values = array_values($array);

        while (preg_match('/%\(([a-zA-Z0-9_ -]+)\)/', $string, $m)) {
            if (!isset($keysmap[$m[1]])) {
                echo "No key $m[1]\n";
                return false;
            }

            $string = str_replace($m[0], '%' . ($keysmap[$m[1]] + 1) . '$', $string);
        }

        array_unshift($values, $string);
        return call_user_func_array('sprintf', $values);
    }
}
